/**
 *  与 地图操作、点位图层（图标点位、圆形点位、聚合点位）、...相关的公共方法
 *  ！important 尽量不要修改，以免牵一发动全身
 *  方法：
 *      _addImageToMap              添加图片资源
 *      _addSourceToMap             添加数据资源
 *      _renderMapLayer             渲染点位图层
 *      _renderMapCluster           渲染聚合图层
 *
 *      _addHoverEventToLayer       添加鼠标悬浮事件
 *      _showPopupOnMap             在地图上展示弹窗
 *      _zoomToCenterWhenShowPopup  移动地图使弹窗点位居中
 *      _initHomeView               还原主视图
 *      _fitBounds                  自适应地图边界
 *
 *      _removeMapLayer             移除图层
 *      _showOrHideMapLayer         显示或隐藏图层
 *      _hideAllMapLayers           隐藏地图所有图层
 *      _hideClusterByName          隐藏指定聚合图层
 *      _hideAllClusterLayer        隐藏所有聚合图层
 *      _removeAllHtmlMarker        移除所有Marker
 *      _removeHtmlMarker           移除指定Marker
 *      _removeMapboxPopup          关闭地图弹窗
 *
 *      _addLocationMarker          添加定位标记（研判分析中心点）
 *      _addPointClickedMarker      添加地图点位选中状态
 *      _getCommonGeoJson           组织GeoJson数据
 *      _initBoundaries             添加行政区划边界
 *      _booleanPointInPolygon      判断点是否在区域内
 */
import config from '@/config'
import { getFpiAssetsFile } from '@/utils/tools'
// import * as turf from '@turf/turf'
const MapUtil = {
  /**
   * 添加图片资源（图片需存放在 assets 目录下）
   * @param imgPath 图片路径
   * @param imgNameArr 图片文件名
   * @param format 图片文件后缀
   * @param type<String> 类型（例：gas(气) | water(水) | earth(土) | ...）
   *
   * 例：图片路径为 'assets/path/myPng.png'，
   * 调用：_addImageToMap('path/', ['myPng'], '.png', 'myType')
   * 执行结果：在地图中添加名为 'myType-myPng' 的图片资源。
   */
  async _addImageToMap(imgPath: string, imgNameArr: string[], format: string, type: string) {
    const imageLoadPromise = [] as any[]
    imgNameArr.forEach(imageName => {
      const imgSource = type === '' ? imageName : type + '-' + imageName
      // window._imgSourcePath 记录图片资源对应的存储路径
      if (!window._imgSourcePath) window._imgSourcePath = {}
      if (!Object.prototype.hasOwnProperty.call(window._imgSourcePath, imgSource)) window._imgSourcePath[imgSource] = imgPath + imageName + format
      if (!window.glMap.hasImage(imgSource)) {
        let image_data
        try {
          image_data = getFpiAssetsFile(imgPath + imageName + format)
        } catch (e) {
          throw new Error(` (_addImageToMap:) 图标文件${imgPath + imageName + format}不存在。\n${e}`)
        }

        const img = new Image()
        // 设置跨域属性以避免Canvas跨域安全错误
        img.crossOrigin = 'anonymous'
        img.src = image_data
        imageLoadPromise.push(
          new Promise((resolve, reject) => {
            img.onload = e => {
              // 避免重复加载
              if (!window.glMap.hasImage(imgSource)) {
                // 在micro-app环境中，直接使用Mapbox原生方法加载图片
                // 避免任何Canvas相关的尺寸验证问题
                window.glMap.loadImage(image_data, (error: any, mapboxImage: any) => {
                  if (error) {
                    try {
                      window.glMap.addImage(imgSource, img)
                      resolve(e)
                    } catch (addImageError) {
                      reject(addImageError)
                    }
                  } else {
                    window.glMap.addImage(imgSource, mapboxImage)
                    resolve(e)
                  }
                })
              } else {
                resolve(e)
              }
            }
            img.onerror = (err) => {
              console.error(`图片 ${imgSource} 加载失败:`, err)
              // 尝试使用Mapbox原生方法作为备选方案
              window.glMap.loadImage(image_data, (error: any, mapboxImage: any) => {
                if (error) {
                  reject(err)
                } else {
                  window.glMap.addImage(imgSource, mapboxImage)
                  resolve(err)
                }
              })
            }
          })
        )
      }
    })
    // 判断是否有图标资源需要加载
    if (imageLoadPromise.length !== 0) {
      await Promise.all(imageLoadPromise)
    }
  },

  /**
   * 添加数据资源（更新数据资源）
   * @param sourceName<string> 资源名称
   * @param jsonData<GeoJson> 地理数据
   * @param options<Object> （可选参数）
   */
  _addSourceToMap(sourceName: string, jsonData: any, options = {}) {
    if (!window.glMap.getSource(sourceName)) {
      window.glMap.addSource(sourceName, {
        type: 'geojson',
        data: jsonData,
        ...options
      })
    } else {
      window.glMap.getSource(sourceName).setData(jsonData)
    }
  },

  /**
   * 渲染点位图层（通过该方法添加的图层，id会存入window._mapLayerIdArr，可以通过_hideAllMapLayers方法统一隐藏）
   * @param layerOption<Object> 参数
   * @param beforeLayerId<String> 置于指定图层下面 （JN1为style文件中的第一个点图层）
   * @param andShow 如果图层已存在，是否直接显示（兼顾水、气的图层加载方式）
   * @returns {string|null} 图层的名称（可在业务组件中对图层做处理。如：添加鼠标事件）
   */
  _renderMapLayer(layerOption: any, beforeLayerId = '', andShow = true): string | null {
    // 判断图层引用的source是否存在
    const layerId = layerOption.id
    const tempSource = layerOption.source
    if (
      !tempSource ||
      (Object.prototype.toString.call(tempSource) === '[object String]' &&
        !window.glMap.getSource(tempSource))
    ) {
      throw new Error(` (_renderMapLayer:) 图层${layerId}指向的资源${tempSource}不存在`)
    }
    // window._mapLayerIdArr 记录加载的图层id
    if (!window._mapLayerIdArr.includes(layerId) && layerId.indexOf('Cluster') === -1) {
      window._mapLayerIdArr.push(layerId)
    }
    // 加载图层
    if (!window.glMap.getLayer(layerId)) {
      if (!window.glMap.getLayer(beforeLayerId)) {
        beforeLayerId = ''
      }
      window.glMap.addLayer(layerOption, beforeLayerId)

      return layerId
    } else {
      // 地图中已经存在该图层
      const { filter, paint, layout } = layerOption
      filter && window.glMap.setFilter(layerId, filter)
      if (paint) {
        Object.keys(paint).forEach((key) => {
          window.glMap.setPaintProperty(layerId, key, paint[key])
        })
      }
      if (layout) {
        Object.keys(layout).forEach((key) => {
          window.glMap.setLayoutProperty(layerId, key, layout[key])
        })
      }
      if (andShow) this._showOrHideMapLayerById(layerId, 'show')
      // 此时不再返回图层名字。（并且无需再次绑定事件）
      return null
    }
  },

  /**
   * 添加鼠标悬浮事件
   * @param layerId<String> 图层名称
   * @param nameField<String> 悬浮显示的字段名称
   */
  _addHoverEventToLayer(layerId: string, nameField = 'name', offset = [0, -15]) {
    window.glMap.on('mouseenter', layerId, (e: any) => {
      window.glMap.getCanvas().style.cursor = 'pointer'
      const coordinates = e.features[0].geometry.coordinates.slice()
      const description = e.features[0].properties[nameField]

      while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
        coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360
      }
      if (window.glTooltip) window.glTooltip.remove()
      window.glTooltip = new window.mapboxgl.Popup({
        className: 'mapbox-tooltip',
        closeOnClick: false,
        closeButton: false,
        offset: offset
      })
        .setLngLat(coordinates)
        .setHTML(description)
        .addTo(window.glMap)
      window.glMap.triggerRepaint()
    })

    window.glMap.on('mouseleave', layerId, function () {
      window.glMap.getCanvas().style.cursor = ''
      window.glTooltip.remove()
    })
  },

  /**
   * 添加鼠标进入样式
   * @param layerId
   * @param cursor
   * @private
   */
  _addHoverCursorToLayer(layerId: string, cursor = '') {
    // 鼠标移入样式
    window.glMap.on('mouseenter', layerId, (e: any) => {
      window.glMap.getCanvas().style.cursor = cursor
    })
    // 鼠标移出清除样式
    window.glMap.on('mouseleave', layerId, (e: any) => {
      window.glMap.getCanvas().style.cursor = ''
    })
  },

  /**
   * 添加定位标记（研判分析中心点）
   * @param coordinates<Array>
   */
  _addLocationMarker(coordinates: any[]) {
    // if (window.locationMarkerWithCircle) window.locationMarkerWithCircle.remove()
    // let el = document.createElement('div')
    // el.className = 'location-marker'
    // let img = require('@/assets/images/map-location.png')
    // let circleImg = require('@/assets/map/gas/alarm-animate.svg')
    // el.innerHTML = `<div class="location-marker-img">
    //                          <img src=${img} style="width: 20px">
    //                          <img style="width:100px;display:block;height:100px;position:absolute;top:-12px;left:-40px;z-index: 900;" src=${circleImg}>
    //                        </div>`
    // // 创建Marker
    // if (window.locationMarkerWithCircle) window.locationMarkerWithCircle.remove()
    // window.locationMarkerWithCircle = new mapboxgl.Marker({
    //   element: el,
    //   anchor: 'bottom',
    //   offset: [0, 8]
    // })
    //   .setLngLat(coordinates)
    //   .addTo(window.glMap)
  },

  /**
   * 判断点位是否在区域内
   * @param coordinates<Array> [lng, lat]
   */
  _booleanPointInPolygon(coordinates: any[], polygonPoints: any[]) {
    // 判断参数是否合法
    const lng = coordinates[0]
    const lat = coordinates[1]
    if (!lng && !lat) return false

    const pt = window.turf.point(coordinates)
    const poly = window.turf.polygon([polygonPoints])
    return window.turf.booleanPointInPolygon(pt, poly)
  },

  /**
   * 添加动态圆图标
   */
  _addPointClickedMarker(coordinates: any[], icon?: string) {
    // const el = document.createElement('div')
    // el.className = 'location-marker'
    // const img = icon || require(`@/assets/map/early-warning.png`)
    // const circleImg = require('@/assets/map/dynamic-circle-blue.svg') // src/assets/map/gas/alarm-animate.svg
    // el.innerHTML = `<div class="location-marker-img">                         
    //                          <img src=${circleImg} style="display:block;position:absolute;transform:translate(-50%, -50%);z-index: 900;">
    //                          <img src=${img} style="position:absolute;transform:translate(-50%, -50%);width:26px;z-index: 900;">
    //                        </div>`
    // // 创建Marker
    // if (window.clickPointMarker) window.clickPointMarker.remove()
    // window.clickPointMarker = new mapboxgl.Marker({
    //   element: el,
    //   anchor: 'bottom'
    //   // offset: [0, 8],
    // })
    //   .setLngLat(coordinates)
    //   .addTo(window.glMap)
    // return el
  },

  /**
   * 在地图上展示弹窗
   * @param coordinates 弹窗位置
   * @param popContent 弹窗实例
   * @param popHeight<Number> （可选参数）如果需要将弹窗移至屏幕中央，此参数设置为弹窗高度
   * @param offset<Array> （可选参数）[x,y] 向右平移 x，向下平移 y
   */
  _showPopupOnMap(coordinates: any[], popContent: any, popHeight?: number, offset = [0, 5], popId?: string) {
    if (popHeight) this._zoomToCenterWhenShowPopup(coordinates, popHeight)
    // 打开弹窗
    if (window.glPopup) window.glPopup.remove()
    window.glPopup = new window.mapboxgl.Popup({
      className: 'map-pop-style',
      closeOnClick: true,
      closeButton: false,
      offset: offset
    })
      .setLngLat(coordinates)
      .setDOMContent(popContent)
      .setMaxWidth('none')
      .addTo(window.glMap)
  },

  /**
   * 移动地图使弹窗点位居中
   * @param coordinates 弹窗坐标
   * @param popupHeight 弹窗高度
   */
  _zoomToCenterWhenShowPopup(coordinates: any[], popupHeight = 450, params = { speed: 0.6, curve: 1.0 }) {
    if (window.glMap.getPitch() === 0) {
      // 二维模式
      const screenLocation = window.glMap.project(coordinates) // lnglat -> xy
      screenLocation.y = screenLocation.y - popupHeight / 2
      const newCoordinates = window.glMap.unproject(screenLocation) // xy -> lnglat
      window.glMap.easeTo({
        center: [newCoordinates.lng, newCoordinates.lat],
        ...params
      })
    } else if (window.glMap.getPitch() > 50) {
      window.glMap.easeTo({
        center: [coordinates[0], coordinates[1] + 0.05],
        ...params
      })
    } else {
      const bounds = window.glMap.getBounds()
      const offsetY = (((bounds._ne.lat - bounds._sw.lat) / window.innerHeight) * popupHeight) / 2
      window.glMap.easeTo({
        center: [coordinates[0], coordinates[1] + offsetY],
        ...params
      })
    }
  },

  /**
   * 渲染聚合图层（常规）
   * @param jsonData<GeoJson> GeoJson数据
   * @param clusterName<String> 聚合名称
   * @param clusterOption<Object> 聚合点样式。如：{type: 'circle', paint: {'circle-color': '#b620e0'}}
   * @param unClusterOption<Object> 未聚合点样式。如：{type: 'circle', paint: {'circle-color': '#0EE37D'}}
   * @param nameField<Object> 悬浮显示的字段名称。（为null时不添加鼠标悬浮事件）
   * @returns {string} 未聚合点图层id（可以在业务组件中对图层做处理。如：添加鼠标事件）
   */
  _renderMapCluster(jsonData: any, clusterName: string, clusterOption: any, unClusterOption: any, nameField: string) {
    // window._mapClusterNameArr 记录聚合名称，可以通过_hideAllClusterLayer方法统一隐藏
    if (!window._mapClusterNameArr.includes(clusterName) && clusterName !== 'camera')
      window._mapClusterNameArr.push(clusterName)

    // step01: 检验参数是否合法：type属性应该取值 'circle' 或 'symbol'
    if (!['circle', 'symbol'].includes(clusterOption.type))
      throw new Error(` (_renderMapCluster:) 参数clusterOption不合法：${clusterOption}`)
    if (!['circle', 'symbol'].includes(unClusterOption.type))
      throw new Error(` (_renderMapCluster:) 参数unClusterOption不合法：${unClusterOption}`)

    // step02: 添加数据资源
    const sourceOption = {
      cluster: true,
      clusterRadius: 80,
      clusterMaxZoom: 13,
      clusterMinPoints: 5
    }
    this._addSourceToMap(clusterName, jsonData, sourceOption)

    // step03-1: 聚合点为圆圈样式。标准示例：clusterOption = {type: 'circle', paint: {'circle-color': '#b620e0'}}
    if (clusterOption.type === 'circle') {
      clusterOption.paint = {
        'circle-color': '#b620e0',
        'circle-radius': [
          'step',
          ['get', 'point_count'],
          12, // 聚合数量小于5的圆半径
          5,
          16, // 聚合数量小于20的圆半径
          20,
          20 // 聚合数量大于20的圆半径
        ],
        ...clusterOption.paint
      }
    }
    // step03-2: 聚合点为图标样式。标准示例：clusterOption = { type: 'symbol', layout: {} }
    if (clusterOption.type === 'symbol') {
      // 添加图标资源
      this._addImageToMap('map/cluster/', [clusterName], '.png', 'cluster')
      clusterOption.layout = {
        'icon-image': 'cluster-' + clusterName,
        'icon-size': 1,
        'icon-ignore-placement': true,
        ...clusterOption.layout
      }
    }

    // step03-3: 添加聚合点图层
    const cluster = {
      id: clusterName + '-Cluster',
      source: clusterName,
      filter: ['has', 'point_count'],
      ...clusterOption
    }
    const clusterLayerId = this._renderMapLayer(cluster, '')
    if (clusterLayerId) {
      // 聚合点图层 点击事件
      window.glMap.on('click', clusterLayerId, (e: any) => {
        const features = window.glMap.queryRenderedFeatures(e.point, { layers: [clusterLayerId] })
        const clusterId = features[0].properties.cluster_id
        window.glMap
          .getSource(clusterName)
          .getClusterExpansionZoom(clusterId, function (err: any, zoom: number) {
            if (err) return
            window.glMap.easeTo({
              center: features[0].geometry.coordinates,
              zoom: zoom
            })
          })
      })
    }

    // step04: 添加聚合数量图层
    const textOffset = clusterOption.type === 'symbol' ? [0.7, 0] : [0, 0]
    const clusterCountOption = {
      id: clusterName + '-Cluster-count',
      type: 'symbol',
      source: clusterName,
      filter: ['has', 'point_count'], // 有point_count属性的，为聚合点
      layout: {
        'text-field': '{point_count}',
        'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
        'text-size': 14,
        'text-allow-overlap': true,
        'text-ignore-placement': true,
        'text-offset': textOffset
      },
      paint: {
        'text-color': '#fff',
        'text-opacity': 1
      }
    }
    this._renderMapLayer(clusterCountOption, '')

    // step05-1: 未聚合点为圆圈样式。标准示例：clusterOption = {type: 'circle', paint: {'circle-color': '#0EE37D'}}
    if (unClusterOption.type === 'circle') {
      unClusterOption.paint = {
        'circle-radius': 6, // 默认圆大小
        'circle-color': '#0EE37D', // 默认圆颜色
        ...unClusterOption.paint
      }
    }
    // step05-2: 未聚合点为图标样式。标准示例：clusterOption = { type: 'symbol', layout: {'icon-image': '{symbolImgName}'} }
    if (unClusterOption.type === 'symbol') {
      if (
        !unClusterOption.layout ||
        !Object.prototype.hasOwnProperty.call(unClusterOption.layout, 'icon-image')
      ) {
        // 需要传参icon-image，同时应该确保地图中存在对应的图片资源
        throw new Error(` (_renderMapCluster:) 参数unClusterOption不合法：${unClusterOption}`)
      }
      unClusterOption.layout = {
        'icon-size': 0.65,
        'icon-ignore-placement': true,
        ...unClusterOption.layout
      }
    }

    // step05-3: 添加未聚合点图层
    const unCluster = {
      id: clusterName + '-unCluster',
      source: clusterName,
      filter: ['!', ['has', 'point_count']],
      ...unClusterOption
    }
    const layerId = this._renderMapLayer(unCluster, clusterName + '-Cluster')
    // 如果有返回值，说明该图层为首次加载，需要继续添加事件
    if (layerId) {
      // 未聚合图层 悬浮事件
      if (nameField === null) return layerId
      this._addHoverEventToLayer(clusterName + '-unCluster')
      // 返回未聚合点图层的名称，在业务组件中添加点击事件
      return layerId
    }
  },

  /**
   * 渲染聚合图层（统计状态）
   * @param jsonData<GeoJson> GeoJson数据
   * @param sourceOption<Object> 聚合资源属性
   * @param clusterName<String> 聚合名称
   * @param clusterOption<Object> 聚合点样式。如：{type: 'circle', paint: {'circle-color': '#b620e0'}}
   * @param unClusterOption<Object> 未聚合点样式。如：{type: 'circle', paint: {'circle-color': '#0EE37D'}}
   * @param displayField<String> 聚合点的字段名称。
   * @param hoverField<String> 悬浮显示的字段名称。（为null时不添加鼠标悬浮事件）
   * @returns {string} 未聚合点图层id（可以在业务组件中对图层做处理。如：添加鼠标事件）
   */
  _renderMapClusterWithCustomField(
    jsonData: any,
    sourceOption: any,
    clusterName: string,
    clusterOption: any,
    unClusterOption: any,
    displayField: string,
    hoverField: string
  ) {
    // window._mapClusterNameArr 记录聚合名称，可以通过_hideAllClusterLayer方法统一隐藏
    if (!window._mapClusterNameArr.includes(clusterName) && clusterName !== 'camera')
      window._mapClusterNameArr.push(clusterName)

    // step01: 检验参数是否合法：type属性应该取值 'circle' 或 'symbol'
    if (!['circle', 'symbol'].includes(clusterOption.type))
      throw new Error(` (_renderMapCluster:) 参数clusterOption不合法：${clusterOption}`)
    if (!['circle', 'symbol'].includes(unClusterOption.type))
      throw new Error(` (_renderMapCluster:) 参数unClusterOption不合法：${unClusterOption}`)

    // step02: 添加数据资源
    const mySourceOption = {
      cluster: true,
      clusterRadius: 80,
      clusterMaxZoom: 13,
      clusterMinPoints: 5,
      ...sourceOption
    }
    this._addSourceToMap(clusterName, jsonData, mySourceOption)

    // step03-1: 聚合点为圆圈样式。标准示例：clusterOption = {type: 'circle', paint: {'circle-color': '#b620e0'}}
    if (clusterOption.type === 'circle') {
      clusterOption.paint = {
        'circle-color': '#b620e0',
        'circle-radius': [
          'step',
          ['get', 'point_count'],
          12, // 聚合数量小于5的圆半径
          5,
          16, // 聚合数量小于20的圆半径
          20,
          20 // 聚合数量大于20的圆半径
        ],
        ...clusterOption.paint
      }
    }
    // step03-2: 聚合点为图标样式。标准示例：clusterOption = { type: 'symbol', layout: {} }
    if (clusterOption.type === 'symbol') {
      // 添加图标资源
      this._addImageToMap('map/cluster/', [clusterName], '.png', 'cluster')
      clusterOption.layout = {
        'icon-image': 'cluster-' + clusterName,
        'icon-size': 1,
        'icon-ignore-placement': true,
        ...clusterOption.layout
      }
    }

    // step03-3: 添加聚合点图层
    const cluster = {
      id: clusterName + '-Cluster',
      source: clusterName,
      filter: ['has', 'point_count'],
      ...clusterOption
    }
    const clusterLayerId = this._renderMapLayer(cluster, '')
    if (clusterLayerId) {
      // 聚合点图层 点击事件
      window.glMap.on('click', clusterLayerId, (e: any) => {
        const features = window.glMap.queryRenderedFeatures(e.point, { layers: [clusterLayerId] })
        const clusterId = features[0].properties.cluster_id
        window.glMap
          .getSource(clusterName)
          .getClusterExpansionZoom(clusterId, function (err: any, zoom: number) {
            if (err) return
            window.glMap.easeTo({
              center: features[0].geometry.coordinates,
              zoom: zoom
            })
          })
      })
    }

    // step04: 添加聚合数量图层
    const textOffset = clusterOption.type === 'symbol' ? [0.7, 0] : [0, 0]
    const textField = `{${displayField}}`
    const clusterCountOption = {
      id: clusterName + '-Cluster-count',
      type: 'symbol',
      source: clusterName,
      filter: ['has', 'point_count'], // 有point_count属性的，为聚合点
      layout: {
        'text-field': textField,
        'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
        'text-size': 14,
        'text-allow-overlap': true,
        'text-ignore-placement': true,
        'text-offset': textOffset
      },
      paint: {
        'text-color': '#fff',
        'text-opacity': 1
      }
    }
    this._renderMapLayer(clusterCountOption, '')

    // step05-1: 未聚合点为圆圈样式。标准示例：clusterOption = {type: 'circle', paint: {'circle-color': '#0EE37D'}}
    if (unClusterOption.type === 'circle') {
      unClusterOption.paint = {
        'circle-radius': 6, // 默认圆大小
        'circle-color': '#0EE37D', // 默认圆颜色
        ...unClusterOption.paint
      }
    }
    // step05-2: 未聚合点为图标样式。标准示例：clusterOption = { type: 'symbol', layout: {'icon-image': '{symbolImgName}'} }
    if (unClusterOption.type === 'symbol') {
      if (
        !unClusterOption.layout ||
        !Object.prototype.hasOwnProperty.call(unClusterOption.layout, 'icon-image')
      ) {
        // 需要传参icon-image，同时应该确保地图中存在对应的图片资源
        throw new Error(` (_renderMapCluster:) 参数unClusterOption不合法：${unClusterOption}`)
      }
      unClusterOption.layout = {
        'icon-size': 0.65,
        'icon-ignore-placement': true,
        ...unClusterOption.layout
      }
    }

    // step05-3: 添加未聚合点图层
    const unCluster = {
      id: clusterName + '-unCluster',
      source: clusterName,
      filter: ['!', ['has', 'point_count']],
      ...unClusterOption
    }
    const layerId = this._renderMapLayer(unCluster, clusterName + '-Cluster')
    // 如果有返回值，说明该图层为首次加载，需要继续添加事件
    if (layerId) {
      // 未聚合图层 悬浮事件
      if (hoverField === null) return layerId
      this._addHoverEventToLayer(clusterName + '-unCluster', hoverField)
      // 返回未聚合点图层的名称，在业务组件中添加点击事件
      return layerId
    }
  },

  /**
   * 隐藏地图所有点位图层 (只隐藏 window._mapLayerIdArr 中记录的 ID)
   */
  _hideAllMapLayers() {
    // this._logErrMessage('MapUtil: 隐藏地图所有点位图层', 'log')
    const tempLayerIdArr = [...window._mapLayerIdArr]
    tempLayerIdArr.forEach((layerId) => {
      this._showOrHideMapLayerById(layerId, 'hide')
    })
  },

  /**
   * 显示或隐藏单个、多个图层
   * @param layerIds<String|Array> 图层名字。String:隐藏单个图层；Array：隐藏多个图层
   * @param showOrHide<String> 显示或隐藏。'show' | 'hide'
   * @private
   */
  _showOrHideMapLayer(layerIds: string | string[], showOrHide: 'show' | 'hide') {
    // 传参为字符串：隐藏单个
    if (Object.prototype.toString.call(layerIds) === '[object String]') {
      const layerId = layerIds as string
      this._showOrHideMapLayerById(layerId, showOrHide)
    }
    // 传参为数组：隐藏多个
    if (Object.prototype.toString.call(layerIds) === '[object Array]') {
      const tempLayerIdArr = [...layerIds]
      tempLayerIdArr.forEach((layerId) => {
        this._showOrHideMapLayerById(layerId, showOrHide)
      })
    }
  },

  /**
   * 移除图层 （支持移除一个或多个图层）
   * @param layerIds<String|Array> 图层名字。String:单个图层；Array：多个图层
   * @private
   */
  _removeMapLayer(layerIds: string | string[]) {
    // 传参为字符串
    if (Object.prototype.toString.call(layerIds) === '[object String]') {
      const layerId = layerIds as string
      this._removeLayerById(layerId)
    }
    // 传参为数组
    if (Object.prototype.toString.call(layerIds) === '[object Array]') {
      const tempLayerIdArr = [...layerIds]
      tempLayerIdArr.forEach((layerId) => {
        this._removeLayerById(layerId)
      })
    }
  },

  /**
   * 移除指定图层
   * @param layerId
   */
  _removeLayerById(layerId: string) {
    const index = window._mapLayerIdArr.indexOf(layerId)
    if (index > -1) window._mapLayerIdArr.splice(index, 1)
    if (window.glMap.getLayer(layerId)) window.glMap.removeLayer(layerId)
  },

  /**
   * 显示或隐藏指定图层
   * @param layerId<string> 图层名称
   * @param showOrHide<String> 显示或隐藏。'show' | 'hide'
   */
  _showOrHideMapLayerById(layerId: string, showOrHide: 'show' | 'hide') {
    // 传参错误
    if (!['show', 'hide'].includes(showOrHide)) {
      throw new Error(` (_showOrHideMapLayerById:) 参数showOrHide不合法：${showOrHide}`)
    }
    const isVisible = showOrHide === 'show' ? 'visible' : 'none'
    // if (showOrHide === 'show') isVisible = 'visible'
    // if (showOrHide === 'hide') isVisible = 'none'

    // // 显示
    // if (isVisible === 'visible' && !_mapLayerIdArr.includes(layerId)) {
    //     _mapLayerIdArr.push(layerId)
    // }
    // // 隐藏
    // if (isVisible === 'none') {
    //     let index = _mapLayerIdArr.indexOf(layerId)
    //     if (index > -1) _mapLayerIdArr.splice(index, 1)
    // }

    if (window.glMap.getLayer(layerId)) {
      window.glMap.setLayoutProperty(layerId, 'visibility', isVisible)
    }
  },

  /**
   * 隐藏所有聚合图层
   */
  _hideAllClusterLayer() {
    // if (window._mapClusterNameArr.length > 0) this._logErrMessage('MapUtil: 隐藏所有聚合图层', 'log')
    const temp = [...window._mapClusterNameArr]
    temp.forEach((clusterName) => {
      this._hideClusterByName(clusterName)
    })
    // _mapClusterNameArr = []
  },

  /**
   * 隐藏指定聚合图层
   * @param clusterName 聚合名称
   */
  _hideClusterByName(clusterName: string) {
    // this._logErrMessage(`清除cluster：${clusterName}`, 'log')
    if (!window.glMap.getSource(clusterName)) return
    // if (window.glMap.getLayer(clusterName + '-clusters')) window.glMap.removeLayer(clusterName + '-Cluster')
    // if (window.glMap.getLayer(clusterName + 'cluster-count')) window.glMap.removeLayer(clusterName + 'Cluster-count')
    // if (window.glMap.getLayer(clusterName + '-UnCluster')) window.glMap.removeLayer(clusterName + '-unCluster')
    this._showOrHideMapLayerById(clusterName + '-Cluster', 'hide')
    this._showOrHideMapLayerById(clusterName + '-Cluster-count', 'hide')
    this._showOrHideMapLayerById(clusterName + '-unCluster', 'hide')
    // 从_mapClusterNameArr中移除该聚合名称
    const index = window._mapClusterNameArr.indexOf(clusterName)
    if (index > -1) window._mapClusterNameArr.splice(index, 1)
  },

  /**
   * 关闭地图弹窗
   */
  _removeMapboxPopup() {
    if (window.glPopup) window.glPopup.remove() // 关闭弹窗
    if (window.glTooltip) window.glTooltip.remove() // 关闭提示
  },

  /**
   * 移除所有的htmlMarker(保存到全局变量 _mapMarkerObjArr 中的 marker)
   */
  _removeAllHtmlMarker() {
    if (window._mapMarkerObjArr) {
      const tempArr = [...window._mapMarkerObjArr]
      tempArr.forEach((markerItem) => {
        markerItem.remove()
      })
      window._mapMarkerObjArr = []
    }
  },

  /**
   * 移除指定的htmlMarker
   * @param markers
   */
  _removeHtmlMarker(markers: any) {
    if (!markers) return
    if (Object.prototype.toString.call(markers) === '[object Array]') {
      markers.forEach((markerItem: any) => {
        markerItem && markerItem.remove()
      })
    } else if (Object.prototype.toString.call(markers) === '[object Object]') {
      Object.keys(markers).forEach((key) => {
        let mk = markers[key]
        if (mk) {
          mk.remove()
          mk = null
          delete markers[key]
        }
      })
    } else {
      console.log('markers不是数组也不是对象')
    }
  },

  /**
   * 还原主视图
   */
  _initHomeView() {
    window.glMap.flyTo({
      ...config.mapInitOptions
    })
  },

  /**
   * 自适应视图
   * @param features<Array> 任何GeoJson数据
   */
  _fitBounds(features: any) {
    const bbox = window.turf.bbox(features)
    const southWest = [bbox[0], bbox[1]]
    const northEast = [bbox[2], bbox[3]]
    window.glMap.fitBounds([southWest, northEast], {
      padding: { top: 50, bottom: 10, left: 600, right: 600 }
    })
  },

  /**
   * 组织GeoJson数据（要素列表套壳）
   * @param features<Array> 要素列表
   */
  _getCommonGeoJson(features:any = []) {
    return {
      type: 'FeatureCollection',
      crs: {
        type: 'name',
        properties: { name: 'urn:ogc:def:crs:OGC:1.3:CRS84' }
      },
      features: features
    }
  },

  /**
   * 判断mapbox图层可见性
   * @param layername
   * @returns {*|boolean true可见，false不可见}
   */
  layerVisibility(layername: string) {
    return (
      window.glMap.getLayer(layername) &&
      window.glMap.getLayoutProperty(layername, 'visibility') !== 'none'
    )
  },
  /**
   * 通过tree-path控制显示/隐藏图层
   * @param data {level,path,visibility}
   * @private
   */
  _showOrHideLayerGroupByTreePath(data: { level: number, path: string, visibility: 'show' | 'hide' }) {
    const { level, path, visibility } = data
    window.glMap.style.stylesheet.layers.forEach((layer: any) => {
      const treePath = layer['tree-path']
      if (treePath) {
        const paths = treePath.split('-')
        paths.length > level &&
          paths[level] === path &&
          this._showOrHideMapLayerById(layer.id, visibility)
      }
    })
  },

  // 添加行政区划边界
  _initBoundaries(geoJsonData: any) {
    const sourceName = 'area_line'
    if (!window.glMap.getSource(sourceName)) {
      window.glMap.addSource(sourceName, { type: 'geojson', data: geoJsonData })
      window.glMap.addLayer(
        {
          id: 'area_line',
          type: 'line',
          source: sourceName,
          paint: {
            'line-width': 2,
            'line-color': '#2ee4f4',
            'line-opacity': 1
          }
        }
      )
      window.glMap.addLayer(
        {
          id: 'area_fill',
          type: 'fill',
          source: sourceName,
          paint: {
            'fill-color': 'rgba(255,255,255,0)',
            'fill-antialias': true
          }
        },
        ''
      )
      return 'area_fill'
    } else {
      window.glMap.getSource(sourceName).setData(geoJsonData)
    }
  },
  
  // 清除地图
  _backInitMap() {
    this._hideAllMapLayers()
    this._removeAllHtmlMarker()
    if (window.glPopup) window.glPopup.remove()
    if (window.glTooltip) window.glTooltip.remove()
  }
}

export default MapUtil
